/*
 * Copyright (C) 2010 ST-Ericsson AS
 * Author: Erwan Bracq / erwan.bracq@stericsson.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <stdint.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <termios.h>
#include <pthread.h>

#ifndef HAVE_ANDROID_OS
#define __u32 uint32_t
#endif
#include <linux/hsi_char.h>

#include "mid_dev.h"
#include "mid_log.h"
#include "mid_settings.h"
#include "mid_error.h"

#define	FLB_HALF 32

#define HSI_COUNTERS_FT_OFFSET 0
#define HSI_COUNTERS_TB_OFFSET 20
#define HSI_COUNTERS_FB_OFFSET 24

struct flb_handle_internal {
	uint8_t dump_buf[FLB_HALF * 4];
	uint8_t zero_buf[1024];
	uint8_t dump_lvl;
	uint8_t scan_lvl;
	long priv_handle;
	void* read_function_ptr;
	void* write_function_ptr;
};

struct mid_dev_internal {
	union {
		struct {
			struct termios old_tio;
			struct termios tio;
		} uart;
		struct mid_usb_data {
			int pipe_fd[2];
			struct flb_handle *flb_handle;
			apr_thread_t* read_td;
			apr_thread_t* write_td;
		} usb;
	} u;
};


struct flb_handle;
struct mid_dev;

struct flb_handle *flb_alloc_hsi_unit(struct mid_dev *);
struct flb_handle *flb_alloc_usb_unit(const char *id, uint16_t vid, uint16_t pid, uint16_t index);

void flb_free_hsi_unit(struct flb_handle *);
void flb_free_usb_unit(struct flb_handle *);

typedef int (*flb_read_data)(struct flb_handle *, void *ptr, int length, int timeout_ms);
typedef int (*flb_write_data)(struct flb_handle *, const void *ptr, int length, int timeout_ms);

static void *
mid_usb_read_td(apr_thread_t* thread, void *arg)
{
	UNUSED(thread);
	struct mid_usb_data *mud = arg;
	struct flb_handle_internal *hdl;
	struct pollfd fds[1];
	uint32_t buffer[256 / 4];
	flb_read_data read_data;
	int error;
	int rem;
	int off;

	off = 0;
	rem = 0;

	hdl = (void *)mud->flb_handle;
	read_data =  (flb_read_data)hdl->read_function_ptr;

	while (1) {

		if (rem == 0) {
			off = 0;
			rem = read_data(mud->flb_handle, buffer, sizeof(buffer), 0);
			if (rem < 0)
				break;
		}

		if (rem > 0) {
			error = write(mud->pipe_fd[1], ((uint8_t *)buffer) + off, rem);
			if (error < 0) {
				fds[0].fd = mud->pipe_fd[1];
				fds[0].events = POLLOUT | POLLERR;
				fds[0].revents = 0;
				error = poll(fds, 1, -1);
				if (error < 0)
					break;
				if (fds[0].revents & (POLLERR | POLLNVAL))
					break;
				continue;
			}
			off += error;
			rem -= error;
		}
	}

	return NULL;
}

static void *
mid_usb_write_td(apr_thread_t* thread, void *arg)
{
	UNUSED(thread);
	struct mid_usb_data *mud = arg;
	struct flb_handle_internal *hdl;
	struct pollfd fds[1];
	uint32_t buffer[256 / 4];
	int error;
	flb_write_data write_data;

	hdl = (void *)mud->flb_handle;
	write_data =  (flb_write_data)hdl->write_function_ptr;

	while (1) {

		error = read(mud->pipe_fd[1], buffer, sizeof(buffer));

		if (error > 0) {
			error = write_data(mud->flb_handle, buffer, error, 0);
			if (error < 0)
				break;
		} else if (error == 0) {
			/* no data */
		} else {
			fds[0].fd = mud->pipe_fd[1];
			fds[0].events = POLLIN | POLLERR;
			fds[0].revents = 0;
			error = poll(fds, 1, -1);
			if (error < 0)
				break;
			if (fds[0].revents & (POLLERR|POLLNVAL))
				break;
		}
	}

	return NULL;
}

/* TODO non blocking mode
static void
mid_usb_set_nb(int f)
{
	int flags;
	flags = fcntl(f, F_GETFL, NULL);
	if (flags == -1)
		return;
	flags |= O_NONBLOCK;
	fcntl(f, F_SETFL, flags);
} */

static int
mid_usb_open(struct mid_dev *dev)
{
	int res;
	struct mid_usb_data *mud;
	apr_status_t status;

	mud = &dev->internal->u.usb;

	memset(mud, 0, sizeof(*mud));

	res = socketpair(AF_LOCAL, SOCK_STREAM, 0, mud->pipe_fd);
	if (res != 0) {
		MLGE("DEV: Failed to create USB pipe: %s.", strerror(errno));
		return (-1);
	}

	if (dev->flb_alloc_usb_unit != NULL)
		mud->flb_handle = (*dev->flb_alloc_usb_unit)(dev->id, dev->vid, dev->pid, dev->index);
	else {
		MLGE("DEV: flb_alloc_usb_unit is NULL. Should not be in this configuration, USB pipe is not created.");
		return (-1);
	}

	if (mud->flb_handle == NULL) {
		close(mud->pipe_fd[0]);
		close(mud->pipe_fd[1]);
		free(mud);
		return (-1);
	}

	status = apr_thread_create(&mud->read_td, NULL, mid_usb_read_td, mud, dev->pool);
	if(status != APR_SUCCESS) {
		char message[100];
		MLGE("DEV: Failed to create USB reader thread: %s",
			apr_strerror(status, message, sizeof(message)));
		close(mud->pipe_fd[0]);
		close(mud->pipe_fd[1]);
		free(mud);
		return (-1);
	}
	status = apr_thread_create(&mud->write_td, NULL, &mid_usb_write_td, mud, dev->pool);
	if(status != APR_SUCCESS) {
		apr_status_t thread_result;
		char message[100];
		MLGE("DEV: Failed to create USB writer thread: %s",
			apr_strerror(status, message, sizeof(message)));
		close(mud->pipe_fd[0]);
		close(mud->pipe_fd[1]);
		if( (status = apr_thread_join(&thread_result, mud->read_td)) != APR_SUCCESS) {
			MLGE("DEV: Failed to join USB read thread: %s",
				apr_strerror(status, message, sizeof(message)));
		}
		free(mud);
		return (-1);
	}

	return (mud->pipe_fd[0]);
}

static int mid_usb_close(struct mid_dev *dev)
{
	int res = 0;
	apr_status_t status;
	apr_status_t thread_result;

	if (close(dev->internal->u.usb.pipe_fd[0]) == -1) {
		res = errno;
		MLGE("DEV: USB pipe #0 close error: %s", strerror(res));
	}

	if (close(dev->internal->u.usb.pipe_fd[1]) == -1) {
		res = errno;
		MLGE("DEV: USB pipe #1 close error: %s", strerror(res));
	}

	if( (status = apr_thread_join(&thread_result, dev->internal->u.usb.write_td)) != APR_SUCCESS) {
		char message[100];
		MLGE("DEV: Failed to join USB write thread: %s",
			apr_strerror(status, message, sizeof(message)));
	}
	if( (status = apr_thread_join(&thread_result, dev->internal->u.usb.read_td)) != APR_SUCCESS) {
		char message[100];
		MLGE("DEV: Failed to join USB read thread: %s",
			apr_strerror(status, message, sizeof(message)));
	}

	return res;
}
static int
mid_hsi_open(struct mid_dev *dev)
{
	int status;
	struct hsi_tx_config tx_conf;
	struct hsi_rx_config rx_conf;
	int fd = 0;

#ifdef DEBUG_TRACE
 	MLGD("DEV: Trying to open HSI device :%s",dev->name);
#endif
	fd = open (dev->name, O_RDWR);
	if (fd < 0) {
		MLGE("DEV: Error trying to open HSI device %s, error: %s",dev->name,strerror(errno));
		return -1;
	}

	if (dev->disable_config) {
#ifdef DEBUG_TRACE
		MLGD("DEV: HSI configuration is disabled for :%s. No HSI option will be loaded.", dev->name);
#endif
		return fd;
	}

	memset(&tx_conf,0x00,sizeof(tx_conf));

	tx_conf.mode = dev->mode;
	tx_conf.flow = dev->flow;
	tx_conf.frame_size = dev->frame_size;
	tx_conf.channels = dev->channels;
	tx_conf.divisor = dev->divisor;
	tx_conf.arb_mode = dev->arb_mode;
	status = ioctl(fd, CS_SET_TX, &tx_conf);
	if (-1 == status)  {
		goto error;
	}

	memset(&rx_conf,0x00,sizeof(rx_conf));

	rx_conf.mode = dev->mode;
	rx_conf.flow = dev->flow;
	rx_conf.frame_size = dev->frame_size;
	rx_conf.channels = dev->channels;
	rx_conf.divisor = dev->divisor;

	rx_conf.counters =
		(180 << HSI_COUNTERS_FT_OFFSET) |
		(7 << HSI_COUNTERS_TB_OFFSET) |
		(8 << HSI_COUNTERS_FB_OFFSET);

	status = ioctl(fd, CS_SET_RX, &rx_conf);
	if (-1 == status)  {
		goto error;
	}
	int state = WAKE_UP;
	status = ioctl(fd, CS_SET_ACWAKELINE, &state);
	if (-1 == status)  {
		goto error;
	}

	return fd;
error:
	if (fd)
		close(fd);
	return -1;
}

static int
mid_hsi_close(struct mid_dev *dev)
{
	int res = 0;

#ifdef DEBUG_TRACE
 	MLGD("DEV: Trying to close HSI device :%s with fd:%d",dev->name,dev->fd);
#endif
	if (dev->fd <= 0) {
		MLGW("DEV: HSI device(%s) already closed",dev->name);
		res = 0;
		goto cleanup;
	}

	int state = WAKE_DOWN;
	ioctl(dev->fd, CS_SET_ACWAKELINE, &state);

	if (close(dev->fd) == -1) {
		res = errno;
		MLGE("DEV: Error trying to close HSI device:%s, error: %s", dev->name,strerror(res));
	}

cleanup:
	return res;
}

static void mid_set_serial_raw(struct termios *termios_p) {
	termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
			| INLCR | IGNCR | ICRNL | IXON);
	termios_p->c_oflag &= ~OPOST;
	termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
	termios_p->c_cflag &= ~(CSIZE | PARENB);
	termios_p->c_cflag |= CS8;
}

int mid_dev_open(struct mid_dev *dev)
{
	int res = 0;
	int fd = -1;
	apr_status_t status;

	if ((status = apr_pool_create(&dev->pool, NULL)) != APR_SUCCESS) {
		char message[100];
		MLGE("DEV: Failed to create memory pool for device: %s. Device cannot be open.",
			apr_strerror(status, message, sizeof(message)));
		return -1;
	}

	if (dev->internal == NULL) {
		MLGE("DEV: Unallocated device internal data structure. mid_configure_dev is maybe missing and this is a MID internal failure..");
		return (-1);
	}

#ifdef DEBUG_TRACE
	MLGD("DEV: MID open device %s. Device type=%d.",dev->name,dev->devtype);
#endif
	switch (dev->devtype) {
	case MID_DEV_SERIAL_96:
	case MID_DEV_SERIAL_115:
	case MID_DEV_SERIAL_460:
	case MID_DEV_SERIAL_921:
		fd = open(dev->name, O_RDWR);
		if (fd == -1) {
			MLGE("DEV: Failed to open tty %s: %s.", dev->name,
			     strerror(errno));
			return -1;
		}

		/* Backup current tio settings */
		if (tcgetattr(fd, &dev->internal->u.uart.old_tio)) {
			res = -1;
			MLGE("DEV: Failed to backup tty settings: %s.",
			     strerror(errno));
			goto err_tcgetattr;
		}

		/* Flush TTY. */
		if (tcflush(fd, TCIOFLUSH)) {
			res = -1;
			MLGE("DEV: Failed to flush tty: %s.", strerror(errno));
			goto err_tcflush;
		}

		/* Apply new termios settings. */
		if (tcsetattr(fd, TCSANOW, &dev->internal->u.uart.tio)) {
			res = -1;
			MLGE("DEV: Failed to apply termios: %s.", strerror(errno));
			goto err_tcsetattr;
		}
		dev->fd = fd;

		return fd;
	case MID_DEV_SPI_13_1024:
		MLGD("DEV: Open SPI device %s.", dev->name);
		fd = open(dev->name, O_RDWR);
		if (fd == -1) {
			MLGE("DEV: Failed to open SPI device %s: %s.", dev->name,
			     strerror(errno));
			return -1;
		}
		dev->fd = fd;
		return fd;
	case MID_DEV_USB:
		fd = mid_usb_open(dev);
		if (fd == -1) {
			MLGE("DEV: Failed to open USB device.");
			return -1;
		}
		dev->fd = fd;
		return fd;
	case MID_DEV_HSI:
		fd = mid_hsi_open(dev);
		if (fd == -1) {
			MLGE("DEV: Failed to open HSI device: %s.",dev->name);
			return -1;
		}
		dev->fd = fd;
		return fd;
	default:
		MLGE("DEV: Device(%d) not supported.",dev->devtype);
		return -ENOTSUP;
	}

err_tcgetattr:
err_tcflush:
err_tcsetattr:
	if (close(fd))
		MLGE("DEV: Failed to close device %s.", dev->name);
	return res;
}

int mid_dev_close(struct mid_dev *dev)
{
	int res = 0;
	MLGD("DEV: Close device %s. Device type:%d.", dev->name, dev->devtype);
	switch (dev->devtype) {
	case MID_DEV_SERIAL_96:
	case MID_DEV_SERIAL_115:
	case MID_DEV_SERIAL_460:
	case MID_DEV_SERIAL_921:
		if (tcsetattr(dev->fd, TCSADRAIN, &dev->internal->u.uart.old_tio)) {
			res = errno;
			MLGE("DEV: Failed to restore termios: %s.", strerror(res));
			/* Try to close device anyway */
		}
		if (close(dev->fd)) {
			res = errno;
			MLGE("DEV: Failed to close device name %s. Error: %s.", dev->name,
				strerror(res));
			goto exit;
		}
		break;
	case MID_DEV_SPI_13_1024:
		if (close(dev->fd)) {
			res = errno;
			MLGE("DEV: Failed to close device name %s. Error: %s", dev->name,
				strerror(res));
			goto exit;
		}
		break;
	case MID_DEV_USB:
		if (mid_usb_close(dev)) {
			MLGE("DEV: Failed to close USB device.");
			res=-EMID;
			goto exit;
		}
		break;
	case MID_DEV_HSI:
		if (mid_hsi_close(dev)) {
			MLGE("DEV: Failed to close HSI device %s.",dev->name);
			res=-EMID;
			goto exit;
		}
		break;
	default:
		MLGE("DEV: Device(%d) type is not supported", dev->devtype);
		res=-ENOTSUP;
		goto exit;
	}

	apr_pool_destroy(dev->pool);
	free(dev->internal);
	dev->internal = NULL;
exit:
	return res;
}

int mid_configure_dev(struct mid_dev *dev, enum mid_dev_type type)
{
	if(dev->internal == NULL)
		dev->internal = calloc(1, sizeof(*(dev->internal)));
	if (dev->internal == NULL)
		return (-1);

	switch (type) {
	case MID_DEV_SERIAL_96:
		memset(&(dev->internal->u.uart.tio), 0, sizeof(struct termios));
		/* Sets the tty to the "raw" mode" input is available character by character,
		 * echoing is disabled, and all special processing of terminal input and output
		 * characters is disabled.
		 */
		mid_set_serial_raw(&(dev->internal->u.uart.tio));
		/* 9600 baud, 8n1, no flow control. */
		dev->internal->u.uart.tio.c_cflag |= (B9600 | UART_CFG);
		dev->internal->u.uart.tio.c_cc[VTIME] = UART_VTIME;
		break;
	case MID_DEV_SERIAL_115:
		memset(&(dev->internal->u.uart.tio), 0, sizeof(struct termios));
		mid_set_serial_raw(&(dev->internal->u.uart.tio));
		/* 115200 baud, 8n1, no flow control. */
		dev->internal->u.uart.tio.c_cflag |= (B115200 | UART_CFG);
		dev->internal->u.uart.tio.c_cc[VTIME] = UART_VTIME;
		break;
	case MID_DEV_SERIAL_460:
		memset(&(dev->internal->u.uart.tio), 0, sizeof(struct termios));
		mid_set_serial_raw(&(dev->internal->u.uart.tio));
		/* 460800 baud, 8n1, no flow control. */
		dev->internal->u.uart.tio.c_cflag |= (B460800 | UART_CFG);
		dev->internal->u.uart.tio.c_cc[VTIME] = UART_VTIME;
		break;
	case MID_DEV_SERIAL_921:
		memset(&(dev->internal->u.uart.tio), 0, sizeof(struct termios));
		mid_set_serial_raw(&(dev->internal->u.uart.tio));
		/* 921600 baud, 8n1, no flow control. */
		dev->internal->u.uart.tio.c_cflag |= (B921600 | UART_CFG);
		dev->internal->u.uart.tio.c_cc[VTIME] = UART_VTIME;
		break;
	case MID_DEV_SPI_13_1024:
	case MID_DEV_USB:
	case MID_DEV_HSI:
		break;
	default:
		MLGE("DEV: Device(%d) not supported.",type);
		return -ENOTSUP;
	}

	dev->devtype = type;
	return 0;
}
